package controller;

import graphicComponent.Graphic2D;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import modelInterfaces.AbstractModelFactory;
import modelInterfaces.IResult;
import modelInterfaces.ISubject;
import modelInterfaces.ITest;
import pluginInterfaces.IMetric;
import utils.Validations;
import view.ViewTestResultGraphic;
import viewInterfaces.IViewTestResultGraphic;
import controllerInterfaces.IControllerTestResultGraphic;

/**
 * Controller class that sets the parameters of the graphic component with the result test.
 * 
 * @author Andy Cullache
 */
public class ControllerTestResultGraphic implements IControllerTestResultGraphic {
	
	/**
	* Generates a graph from a test. generates a graph for each metric and a 
	* curve for each algorithm associated with each metric.
	* 
	* @param test      test containing the results to plot.
	*/
	public void generateGraphics(ITest test){
		Map<Integer, Double> mapAlgithmResult;
		List<Map<Integer, Double>> listMap = new ArrayList<>();
		Set<String> metrics = new HashSet<String>();
		for(List<IResult> resultList: test.getResultMap().values()){
			for(IResult result : resultList){
				metrics.add(result.getMetric().getName());
			}
		}

		for(String metric : metrics){
			double max = 0;
			IViewTestResultGraphic view;
			Graphic2D graphic = null;
			listMap = new ArrayList<>();
			for(String algorithm: test.getResultMap().keySet()){
				mapAlgithmResult = setupMap(test,algorithm,metric);
				int[] variables = new int[mapAlgithmResult.size()];
				int i = 0;
				for(int alg : mapAlgithmResult.keySet()){
					variables[i] = alg;
					i++;
				}
				for(double average : mapAlgithmResult.values()){
					if(average>max)
						max = average;
				}
				if(graphic == null){
					graphic = new Graphic2D(variables);
				}
				listMap.add(mapAlgithmResult);
			}
			graphic.setMaxY(max);
			graphic.setResultAlgorithm(listMap);
			view = new ViewTestResultGraphic();
			view.setGraphic(graphic);
			view.SetController(this);
			view.setMetric(metric);
			view.setAlgorithms(test.getResultMap().keySet());
		}		
	}
	
	/**
	* Average each value of dependent variable (X axis) by 
	* adding all the results for a specific algorithm and metric for all test users. 
	* curve for each algorithm associated with each metric.
	* 
	* @param test      test containing the results to plot.
	* @param algorithm      algorithm to consider to average values.
	* @param metric      metric to consider to average values.
	*/
	private Map<Integer, Double> setupMap(ITest test, String algorithm, String metric){
		Map<Integer, Double> mapAlgorithmResult = new HashMap<Integer, Double>();
		for(IResult res: test.getResultMap().get(algorithm)){
			int variable = res.getVariable();
			if(!mapAlgorithmResult.containsKey(variable) && res.getMetric().getName().equals(metric)){
				mapAlgorithmResult.put(variable, res.getResultAverage());
			}
			else if(res.getMetric().getName().equals(metric)){
				double aux = mapAlgorithmResult.get(variable);
				aux = (aux + res.getResultAverage())/2;
				mapAlgorithmResult.put(variable, aux);
			}
		}
		return mapAlgorithmResult;
	}

	/**
	* Generates a graph from a test result from CSV file. generates a graph for each metric and a 
	* curve for each algorithm associated with each metric. Invokes generateGraphics(ITest) method,
	* for this first create a test and assigns the results read from the file.
	* 
	* @param dir      PATH of CSV file.
	* @param filename      name of CSV file.
	*/
	@Override
	public boolean generateGraphics(String dir, String fileName) {
		ITest test = readTestResult(dir, fileName);
		if(test != null){
			generateGraphics(test);
			return true;
		}
		return false;
		
	}
	
	/**
	* Reads the results of tests from a CSV file and add the test with this results.
	* 
	* @param dir      PATH of the file.
	* @param filename      name of the file.
	* @return      true if read test result from CSV file.
	*/
	private ITest readTestResult(String dir, String fileName) {
		ITest testAux = AbstractModelFactory.getModelFactory().getTest();
		testAux.setDirPath(dir);
		testAux.setOutputFile(fileName);
		if(Validations.isValidTestLoad(testAux)){
			boolean ret= readTestFromCSV(testAux);
			if(ret){
				return testAux;
			}
		}
		return null;
	}
	
	/**
	* Reads the results of tests from a CSV file, read the results associated with test given as a parameter.
	* 
	* @param test      test to which are added the results of the file.
	* @return      true if read test result from CSV file.
	*/
	private boolean readTestFromCSV(ITest test){
	    	File fileCSV = null;
	        FileReader fr = null;
	        BufferedReader br = null;
	
	        try {
	           fileCSV = new File (test.getDirPath(),test.getOutputFile());
	           if(!fileCSV.canRead())
	        	   return false;
	           fr = new FileReader (fileCSV);
	           br = new BufferedReader(fr);
	
	           String firstLine;
	           firstLine = br.readLine();
	           String[] data = firstLine.split(",");           
	           
	           String line;
	           String algAux = "";
	    	   List<IResult> resMetList = null;
	           while((line=br.readLine())!=null){
	        	   String[] values = line.split(",");
	        	   test.setName(values[0]+" (loaded)");
	        	   if(!algAux.equals(values[1])){
		        	   algAux = values[1];
		        	   resMetList = new ArrayList<IResult>();
		        	   test.getResultMap().put(algAux, resMetList);
	        	   }
	    		   ISubject subject = AbstractModelFactory.getModelFactory().getUser();
	    		   subject.setId(Integer.parseInt(values[2]));
	    		   int variable = Integer.parseInt(values[3]);
	        	   for(int i=4; i<data.length; i++){
	        		   IMetric metSet = AbstractModelFactory.getModelFactory().getMetricSetup();
	        		   IResult resMet = AbstractModelFactory.getModelFactory().getResultMetric();
	        		   resMet.setVariable(variable);
	        		   resMet.setSubject(subject);
	        		   metSet.setName(data[i]);
	        		   resMet.setMetric(metSet);
	        		   resMet.setResultAverage(Double.parseDouble(values[i]));
	        		   resMetList.add(resMet);
	        	   }
	           }
	           return true;
	        }
	        catch(Exception e){
	           e.printStackTrace();
	        }finally{
	           try{                    
	              if( null != fr ){   
	                 fr.close();     
	              }                  
	           }catch (Exception e2){ 
	              e2.printStackTrace();
	           }
	        }
	        return false;
	    }

}
